package store;

import java.io.File;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.net.URISyntaxException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.jdom.output.Format;
import org.jdom.output.XMLOutputter;
import org.jdom.xpath.XPath;
import dataview.CriterioRicerca;
import dataview.Ordinamento;
import dataview.PerStringa;
import dataview.TipoTermine;
import exceptions.DBException;

public abstract class BaseStore {
	private static String HOME = System.getProperty("user.home") + "/easycooking";
	public static String XSD = HOME + "/database/easycooking.xsd", DB = HOME + "/database/database.xml", DB_TMP = DB + ".tmp";
	protected static Namespace NS = Namespace.getNamespace("xs", "http://www.w3.org/2001/XMLSchema");
	private static Document database;
	private static boolean modificato = false;
	
	public static boolean isModificato() {
		return modificato;
	}
	
	public static boolean isModificato(Object dato) {
		Element element = null;
		try {
			if (dato instanceof Cibo) {
				Cibo cibo = (Cibo)dato;
				element = (Element)XPath.selectSingleNode(getDB(), "/easycooking/cibi/cibo[@nome='" + cibo.getNome() + "']");
				if (element == null) return true;
				Cibo e = Cibo.fromXml(element);
				if (cibo.getInfo().equals(e.getInfo()) && cibo.getTag().equals(e.getTag())) return false;
			}
			if (dato instanceof Ricetta) {
				Ricetta ricetta = (Ricetta)dato;
				element = (Element)XPath.selectSingleNode(getDB(), "/easycooking/ricette/ricetta[@nome='" + ricetta.getNome() + "']");
				if (element == null) return true;
				Ricetta e = Ricetta.fromXml(element, false);
				if (ricetta.getInfo().equals(e.getInfo()) && ricetta.getProcedimento().equals(e.getProcedimento()) && ricetta.getTag().equals(e.getTag()))
					return false;
			}
			if (dato instanceof Menu) {
				Menu menu = (Menu)dato;
				element = (Element)XPath.selectSingleNode(getDB(), "/easycooking/menus/menu[@nome='" + menu.getNome() + "']");
				if (element == null) return true;
				Menu e = Menu.fromXml(element, false);
				if (menu.getInfo().equals(e.getInfo()) && menu.getPortate().equals(e.getPortate()) && menu.getTag().equals(e.getTag())) return false;
			}
		} catch (Exception e) {
			e.printStackTrace();
			return false;
		}
		return true;
	}
	
	public static void setUp() throws DBException, IOException, URISyntaxException {
		//creo la cartella del DB se non esiste
		new File(HOME + "/database").mkdirs();
		if (!new File(XSD).exists()) {
			InputStream fis = ClassLoader.getSystemResourceAsStream("easycooking.xsd");
			FileWriter fw = new FileWriter(XSD);
			int c;
			while ((c = fis.read()) != -1)
				fw.write(c);
			fw.close();
			fis.close();
		}
		
		//inizializzo il db
		Element ec = new Element("easycooking");
		ec.addContent(new Element("cibi"));
		ec.addContent(new Element("ricette"));
		ec.addContent(new Element("menus"));
		Element terminologia = new Element("terminologia");
		Element settings = new Element("settings");
		PerStringa criterio = new PerStringa(Ordinamento.crescente, "", true);
		settings.addContent(new Element("ricerca_cibi").addContent(criterio.toXml()));
		settings.addContent(new Element("ricerca_ricette").addContent(criterio.toXml()));
		settings.addContent(new Element("ricerca_menu").addContent(criterio.toXml()));
		settings.addContent(new Element("salvataggi").setText("0"));
		settings.addContent(new ValutazioniRicettaAttive().toXml());
		settings.addContent(new ValutazioniMenuAttive().toXml());
		for (TipoTermine tipo : TipoTermine.values())
			terminologia.addContent(new Element(tipo.name()));
		ec.addContent(terminologia);
		ec.addContent(settings);
		database = new Document(ec);
		save(true);
	}
	
	protected static Document getDB() throws DBException {
		if (database == null) reload(false);
		return database;
	}
	
	protected static void reload(boolean temporaneo) throws DBException {
		try {
			database = new SAXBuilder().build(new File(temporaneo ? DB_TMP : DB));
		} catch (Exception e) {
			throw new DBException(e);
		}
	}
	
	public static void recuperaBackup() throws DBException {
		File backup = new File(DB_TMP);
		if (!backup.exists()) throw new IllegalArgumentException();
		modificato = true;
		reload(true);
	}
	
	/**
	 * Salva le modifiche fatte all'oggetto in questione. IMPORTANTE ASSUNZIONE: l'oggetto in
	 * questione non pu� cambiare nome ed � gi� presente nel DB
	 * 
	 * @param dato
	 * @throws DBException
	 */
	public static boolean salvaModifiche(Object dato) throws DBException {
		Element element = null;
		try {
			if (dato instanceof Cibo) {
				Cibo cibo = (Cibo)dato;
				element = (Element)XPath.selectSingleNode(getDB(), "/easycooking/cibi/cibo[@nome='" + cibo.getNome() + "']");
				Cibo e = Cibo.fromXml(element);
				if (cibo.getInfo().equals(e.getInfo()) && cibo.getTag().equals(e.getTag())) return false;
				getDB().getRootElement().getChild("cibi").removeContent(element);
				getDB().getRootElement().getChild("cibi").addContent(cibo.toXml());
				save(false);
				return true;
			}
			if (dato instanceof Ricetta) {
				Ricetta ricetta = (Ricetta)dato;
				element = (Element)XPath.selectSingleNode(getDB(), "/easycooking/ricette/ricetta[@nome='" + ricetta.getNome() + "']");
				Ricetta e = Ricetta.fromXml(element, false);
				if (ricetta.getInfo().equals(e.getInfo()) && ricetta.getProcedimento().equals(e.getProcedimento()) && ricetta.getTag().equals(e.getTag()))
					return false;
				getDB().getRootElement().getChild("ricette").removeContent(element);
				getDB().getRootElement().getChild("ricette").addContent(ricetta.toXml());
				save(false);
				return true;
			}
			if (dato instanceof Menu) {
				Menu menu = (Menu)dato;
				element = (Element)XPath.selectSingleNode(getDB(), "/easycooking/menus/menu[@nome='" + menu.getNome() + "']");
				Menu e = Menu.fromXml(element, false);
				if (menu.getInfo().equals(e.getInfo()) && menu.getPortate().equals(e.getPortate()) && menu.getTag().equals(e.getTag())) return false;
				getDB().getRootElement().getChild("menus").removeContent(element);
				getDB().getRootElement().getChild("menus").addContent(menu.toXml());
				save(false);
				return true;
			}
		} catch (Exception e) {
			throw new DBException(e);
		}
		throw new IllegalArgumentException("E' possibile salvare solo oggetti di tipo Cibo, Ricetta o Menu");
	}
	
	public static void save(boolean permanente) throws DBException {
		save(permanente, false);
	}
	
	public static void saveBackup() throws DBException {
		save(true, true);
	}
	
	private static void save(boolean permanente, boolean backup) throws DBException {
		checkValidation(getDB());
		if (permanente) {
			try {
				new XMLOutputter(Format.getPrettyFormat()).output(getDB(), new FileOutputStream(DB + (backup ? ".tmp" : "")));
			} catch (Exception e) {
				throw new DBException(e);
			}
			reload(backup);
		}
		//XXX
		if (!backup) modificato = !permanente;
//			modificato = true;
	}
	
	public static void checkValidation(Document document) throws DBException {
		try {
			Validator validator = SchemaFactory.newInstance(NS.getURI()).newSchema(new File(XSD)).newValidator();
			validator.validate(new StreamSource(new StringReader(new XMLOutputter(Format.getPrettyFormat()).outputString(document))));
		} catch (Exception e) {
			try {
				new XMLOutputter(Format.getPrettyFormat()).output(document, new FileWriter(HOME + "/database/error.xml"));
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			throw new DBException(e);
		}
	}
	
	public static int getSettingsSalvataggi() {
		Element s = null;
		try {
			s = getDB().getRootElement().getChild("settings").getChild("salvataggi");
		} catch (DBException e) {
			e.printStackTrace();
		}
		if (s == null) return 0;
		return Integer.parseInt(s.getText());
	}
	
	public static void setSettingsSalvataggi(int s) {
		Element e;
		try {
			e = getDB().getRootElement().getChild("settings").getChild("salvataggi");
			e.setText(Integer.toString(s));
			save(false);
		} catch (DBException e1) {
			e1.printStackTrace();
		}
	}
	
	public static void setSettingsValutazioneRicette(ValutazioniRicettaAttive valutazione) throws DBException {
		if (valutazione == null) return;
		//assumo che valutazioni ricette sia salvato prima di valutazioni menu e valutazioni menu � l'ultimo elemento di settings
		//workaround
		getDB().getRootElement().getChild("settings").removeChild("valutazione_ricette");
		Element e = getDB().getRootElement().getChild("settings").getChild("valutazione_menu");
		getDB().getRootElement().getChild("settings").removeChild("valutazione_menu");
		getDB().getRootElement().getChild("settings").addContent(valutazione.toXml());
		getDB().getRootElement().getChild("settings").addContent(e);
		save(false);
	}
	
	public static ValutazioniRicettaAttive getSettingsValutazioneRicette() throws DBException {
		return ValutazioniRicettaAttive.fromXml(getDB().getRootElement().getChild("settings").getChild("valutazione_ricette"));
	}
	
	public static void setSettingsValutazioneMenu(ValutazioniMenuAttive valutazione) throws DBException {
		if (valutazione == null) return;
		getDB().getRootElement().getChild("settings").removeChild("valutazione_menu");
		getDB().getRootElement().getChild("settings").addContent(valutazione.toXml());
		save(false);
	}
	
	public static ValutazioniMenuAttive getSettingsValutazioneMenu() throws DBException {
		return ValutazioniMenuAttive.fromXml(getDB().getRootElement().getChild("settings").getChild("valutazione_menu"));
		
	}
	
	public static void setSettingsCibi(CriterioRicerca criterio) {
		if (!getSettingsCibi().equals(criterio)) setSettings(criterio, "ricerca_cibi");
	}
	
	public static void setSettingsRicette(CriterioRicerca criterio) {
		if (!getSettingsRicette().equals(criterio)) setSettings(criterio, "ricerca_ricette");
	}
	
	public static void setSettingsMenu(CriterioRicerca criterio) {
		if (!getSettingsMenu().equals(criterio)) setSettings(criterio, "ricerca_menu");
	}
	
	public static CriterioRicerca getSettingsCibi() {
		return getSettings("ricerca_cibi");
	}
	
	public static CriterioRicerca getSettingsRicette() {
		return getSettings("ricerca_ricette");
	}
	
	public static CriterioRicerca getSettingsMenu() {
		return getSettings("ricerca_menu");
	}
	
	private static CriterioRicerca getSettings(String dato) {
		try {
			return CriterioRicerca.fromXml((Element)getDB().getRootElement().getChild("settings").getChild(dato).getChildren().get(0));
		} catch (DBException e) {
			System.err.println(e);
		}
		return null;
	}
	
	private static void setSettings(CriterioRicerca criterio, String dato) {
		try {
			getDB().getRootElement().getChild("settings").getChild(dato).removeContent();
			getDB().getRootElement().getChild("settings").getChild(dato).addContent(criterio.toXml());
			save(false);
		} catch (DBException e) {
			System.err.println(e);
		}
	}
}
